#pragma once
#include "../comm/comm.hpp"
#include "../comm/log.hpp"
#include "model.hpp"
#include "view.hpp"

using namespace httplib;
class Machine
{
private:
    std::string ip_;
    int port_;
    int load_;

    std::mutex *load_mtx_;

public:
    Machine(const std::string &ip = "", int port = 0, int load = 0) : ip_(ip), port_(port), load_(load)
    {
        load_mtx_ = new std::mutex();
    }
    void IncLoad()
    {
        std::unique_lock<std::mutex> ul(*load_mtx_);
        ++load_;
    }
    void DecLoad()
    {
        std::unique_lock<std::mutex> ul(*load_mtx_);
        --load_;
    }
    std::string GetIp()
    {
        return ip_;
    }
    int GetPort()
    {
        return port_;
    }
    int GetLoad()
    {
        return load_;
    }
    void ResetLoad()
    {
        load_ = 0;
    }
    ~Machine()
    {
        // delete load_mtx_;
    }
};

const std::string confpath = "./conf/server_machine.conf";

class LoadBalance
{
private:
    // 下标作为机器的id
    std::vector<Machine> machines_;
    // 在线和离线中存储的就是机器的id，也就是下标，在哪个数组中就处于哪种状态
    std::vector<int> online_;
    std::vector<int> offline_;

    std::mutex mtx_;

    bool LoadConf(const std::string &conf_path)
    {
        std::ifstream in(conf_path);
        if (!in.is_open())
        {
            LOG(LogError, "%s中第%d行，打开配置文件%s失败\n", __FILE__, __LINE__, CSTR(conf_path));
            return false;
        }

        std::string line;
        while (std::getline(in, line))
        {
            std::vector<std::string> tokens;
            StringTool::Spilit(line, &tokens, ":");
            if (tokens.size() != 2)
            {
                LOG(LogWarning, "%s中第%d行，配置文件格式错误\n", __FILE__, __LINE__);
                continue;
            }

            online_.push_back(machines_.size());
            machines_.push_back(Machine(tokens[0], std::stoi(tokens[1]), 0));
        }

        LOG(LogInfo, "加载配置文件成功\n");
        in.close();
        return true;
    }

public:
    LoadBalance()
    {
        assert(LoadConf(confpath));
    }

    bool Choice(int *id, Machine **m)
    {
        std::unique_lock<mutex> ul(mtx_);
        if (online_.size() == 0)
        {
            LOG(LogFatal, "%s中第%d行，没有机器可用\n", __FILE__, __LINE__);
            return false;
        }

        *id = online_[0];
        *m = &machines_[online_[0]];
        for (int i = 1; i < online_.size(); i++)
        {
            int cur_load = machines_[online_[i]].GetLoad();
            if ((*m)->GetLoad() > cur_load)
            {
                *id = online_[i];
                *m = &machines_[online_[i]];
            }
        }

        return true;
    }

    void OffLineMachine(int id)
    {
        std::unique_lock<mutex> ul(mtx_);
        for (auto iter = online_.begin(); iter != online_.end(); ++iter)
        {
            if (id == *iter)
            {
                machines_[*iter].ResetLoad();
                online_.erase(iter);
                offline_.push_back(id);
                break;
            }
        }
    }


    void ReLoad()
    {
        std::unique_lock<mutex> ul(mtx_);
        online_.clear();
        offline_.clear();
        machines_.clear();
        LoadConf(confpath);
    }


    ~LoadBalance() {}
};


class Control
{
public:

    Control()
    {
    }

    void RestoreMachine()
    {
        load_balance_.ReLoad();
    }

    bool AllQuestions(std::string *html)
    {
        vector<Question> q;
        if (model_.GetAllQuestion(&q))
        {
            view_.AllQuestionsHtml(q, html);
            return true;
        }
        else
        {
            LOG(LogFatal, "题目列表加载错误\n");
            return false;
        }
    }

    bool OneQuestion(const std::string &number, std::string *html)
    {
        Question q;
        if (model_.GetNumQuestion(number, &q))
        {
            view_.OneQuestionHtml(q, html);
            return true;
        }
        else
        {
            LOG(LogWarning, "没有对应编号题目\n");
            return false;
        }
    }

    bool Judge(const std::string &number, const std::string &in_json, std::string *out_json)
    {
        // 1. 根据题目编号，直接拿到对应的题目细节
        Question q;
        model_.GetNumQuestion(number, &q);

        // 2. in_json反序列化，其中包括用户编写的代码，和用户的输入
        Json::Value in;
        Json::Reader reader;
        reader.parse(in_json, in);
        std::string header = in["code"].asString();

        // 3. 拼接用户的代码和测试用例代码，组合形成新的代码, 生成编译服务所要的数据
        Json::Value compile_value;
        Json::FastWriter writer;
        compile_value["code"] = header + q._tail;
        compile_value["input"] = in["input"].asString();
        compile_value["cpu_limit"] = q._cpu_limit;
        compile_value["mem_limit"] = q._mem_limit;
        std::string compile_str = writer.write(compile_value);
        // LOG(LogInfo, "要给编译服务的字符串：%s\n", CSTR(compile_str));

        // 4. 选择负载最低的代码
        //  规则：一直选择，知道被选择主机可用，否则就是机器全部挂掉
        while (true)
        {
            int id = 0;
            Machine *m;
            if (!load_balance_.Choice(&id, &m))
            {
                // 所有主机全部挂掉
                // LOG(LogInfo, "所有主机全部离线\n");
                break;
            }
            LOG(LogInfo, "选择主机成功，id：%d，详情：%s:%d\n", id, CSTR(m->GetIp()), m->GetPort());

            // 5. 发起http请求，得到结果
            //  网络发送要进行差错处理，假如发送失败了怎么办？
            Client client(m->GetIp(), m->GetPort());
            // LOG(LogInfo, "%s:%d\n", CSTR(m->GetIp()), m->GetPort());
            // 请求时，这台机器的负载就已经要被增加了
            m->IncLoad();
            if (auto res = client.Post("/compile_and_run", compile_str.c_str(), compile_str.size(), "application/json;charset=utf-8"))
            {
                // 6. 将结果赋给out_json
                if (res->status == 200)
                {
                    *out_json = res->body;
                    // 请求完毕，负载减小
                    // LOG(LogInfo, "请求完毕，返回结果：%s\n", CSTR(res->body));
                    m->DecLoad();
                    break;
                }
                LOG(LogError, "http请求发送失败\n");
                m->DecLoad();
            }
            else
            {
                // 这台被选中的机器可能挂掉了
                LOG(LogInfo, "主机，id：%d，详情：%s:%d 可能被离线了\n", id, CSTR(m->GetIp()), m->GetPort());
                load_balance_.OffLineMachine(id);
            }
        }
        return true;
    }

    ~Control()
    {
    }

private:
    Model model_;
    View view_;
    LoadBalance load_balance_;
};